package com.zxl.common.lock;


import cn.hutool.extra.spring.SpringUtil;
import com.zxl.common.callback.BusinessLockCallBack;
import com.zxl.common.exception.BusinessException;
import com.zxl.common.utils.RedisUtils;
import com.zxl.common.utils.StrUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.Objects;

/**
 * 业务锁实现
 * @author chenping4
 */
@Slf4j
public class BusinessLock {


    private RedisUtils redisUtils;

    private volatile static BusinessLock self;


    @Deprecated
    public BusinessLock(){
        redisUtils = SpringUtil.getBean(StrUtils.firstLowerCase(RedisUtils.class.getSimpleName()));
    }

    public static BusinessLock newInstance(){
        if (self == null) {
            synchronized(BusinessLock.class) {
                if (self == null) {
                    self = new BusinessLock();
                    self.setRedisUtils(SpringUtil.getBean(StrUtils.firstLowerCase(RedisUtils.class.getSimpleName())));
                }
            }
        }
        return self;
    }

    private void setRedisUtils(RedisUtils redisUtils) {
        this.redisUtils = redisUtils;
    }

    /**
     * 业务排它锁
     * @param key
     * @param businessLockCallBack
     * @return
     */
    public Object lock(String key, BusinessLockCallBack businessLockCallBack){
        validKey(key);
        boolean executor = false;
        try {
            //如果业务执行中，锁被抢返回false，获得锁返回true
            executor = preMethod(key,120);
            if(!executor){
                throw new BusinessException("业务处理中,请勿多次重复提交!");
            }
            return businessLockCallBack.coreBusiness();
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException(e.getLocalizedMessage());
        } finally {
            //执行完成删除redis缓存,执行业务的才需要进行清空缓存，没有获取锁的业务略过
            if(executor){
                postMethod(key);
            }
        }
    }


    private void validKey(String key){
        if(StringUtils.isBlank(key)){
            throw new BusinessException("key param can not be empty");
        }
    }

    /**
     * 前置方法
     * 避免重复提交操作
     * @param key
     * @return
     */
    private boolean preMethod(String key,long seconds){
        if(StringUtils.isBlank(key)){
            return false;
        }
        RedisTemplate redisTemplate = SpringUtil.getBean(StrUtils.firstLowerCase(RedisTemplate.class.getSimpleName()));
        long lockTime = seconds*1000;
        return (Boolean) redisTemplate.execute((RedisCallback) connection -> {
            long expireAt = System.currentTimeMillis() + lockTime + 1;
            Boolean acquire = connection.setNX(key.getBytes(), String.valueOf(expireAt).getBytes());
            if (acquire) {
                //设置成功标识没有锁
                return true;
            } else {
                //已加锁
                byte[] value = connection.get(key.getBytes());
                if (Objects.nonNull(value) && value.length > 0) {
                    long expireTime = Long.parseLong(new String(value));
                    if (expireTime < System.currentTimeMillis()) {
                        // 加锁时间过期
                        byte[] oldValue = connection.getSet(key.getBytes(), String.valueOf(System.currentTimeMillis() + lockTime + 1).getBytes());
                        // 防止死锁
                        return Long.parseLong(new String(oldValue)) < System.currentTimeMillis();
                    }
                }
            }
            return false;
        });
    }

    /**
     * 后置方法 ，释放锁
     * @param key
     * @return
     */
    private void postMethod(String key){
        if(StringUtils.isBlank(key)){
            return;
        }
        redisUtils.del(key);
    }
}

